spec: ADR-0122 phase 2 — flip bare names to parsed semantics (#6083) - #6279
Conversation
#6083) Flips all 1384 bare `export type X = z.infer<typeof XSchema>` aliases in `packages/spec/src/**/*.zod.ts` to `z.input`, so `const c: Connector = { … }` is correct by default in every domain — the ADR-0033 keystroke argument that decided ADR-0122. Phase 1 (#5551 / PR #6072) had already given every schema with two distinct shapes its `XParsed` name, so nothing is stranded by the move. Retires 102 `XInput` aliases. After the flip each one denotes character-for- character what its bare name denotes, and D3 forbids a permanent synonym. Nine `*Input` names survive because they are not synonyms: four are the bare aliases of their own `…InputSchema`, five are composed (recursive / `Partial`-shaped) types no bare alias denotes. Inverts `check:spec-parsed-alias` rather than extending it. The flip empties the population the phase-1 gate was written over, and measured on this tree that gate reports 0 coverage findings (vacuously green) and 754 stale-pin findings (all false) — one arm silently stops working while the other misfires on the whole registry. The gate now refuses a bare name that reads `z.infer` (the flip, enforced), refuses an `XInput` synonym of a bare name (the retirement, enforced), and keeps the paired-or-pinned and stale-pin arms on the flipped form. Inverting it widened it, and the widening found real work: the 86 aliases that already read `z.input` had never been asked whether their parsed state was named. 57 were neither paired nor pinned; the same type-level probe that chose phase 1's split (with its control assertion, which failed as required) split them 22 differ / 35 isomorphic. The 22 gained an `XParsed`, the 35 became pins — 719 → 754. That is #5507's remaining scope, absorbed here. Consumer migration is compiler-named except in one place: a parse result is structurally assignable to the author state, so a function's declared return type can silently start promising less. 24 `defineX` factories were therefore re-declared as `XParsed` by hand; every other migration in this change was a tsc error first. Evidence the direction is right: `test-typecheck-debt.json`, whose own header describes its contents as fixture literals annotated with the OUTPUT type while holding an authored INPUT literal, went from 79 files / 691 errors to 59 / 270 without a single fixture being edited. No runtime behaviour changes. `json-schema/` and `authorable-surface/` are byte-identical — those generators read runtime `z.ZodType` exports, never aliases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
Everything here is a site where a value's type said "defaults applied" and the flip made that claim testable. Three shapes: 1. **Holders of a parse result** move to `XParsed` — `buildAccessMatrix`'s return, the health-monitor / hot-reload internal config maps, `DEFAULT_METADATA_TYPE_REGISTRY`, `MetadataManager.typeRegistry`, `MigrationExecutor`'s operations, the seed-loader service's request/result/graph plumbing, the FLS mask the permission evaluator builds. Each was `z.infer` before the flip; naming `XParsed` restores exactly that and changes no runtime behaviour. 2. **Readers of a defaulted key off an author-state value** state the schema's own default at the read — `ObjectLogger`'s `rotation`, the REST server's three nested operation maps, `ScriptBody.capabilities`, the delegated-admin report's flags. Per key, not per object: the schemas default *inside* those objects, so `??` on the whole object only ever filled a wholly absent one. 3. **Factories the compiler cannot name.** `createEvalUser` returns a complete user and declared `EvalUser`; a parse result is assignable to the author state, so it kept compiling while promising less. Now `EvalUserParsed`, like the 24 `defineX` factories. `IDataEngine.find`/`findOne` keep the parsed query type. Loosening them to accept the author state is a real improvement and deliberately NOT taken here: it needs the engine to apply `SortNode`'s `order` default at runtime, which is a behaviour change and belongs in its own change. Also updates the three gates that pinned a retired spelling: the `EnvironmentArtifact` dual-source pin (which named `EnvironmentArtifactInput`), `def-key-collisions`' mutation anchor, and `connector-author-shape`, whose "`ConnectorInput` is the author shape" probes now read `Connector` / `ConnectorParsed` with the literal and both verdicts unchanged — the flip's claim as a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 15 package(s): 133 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The remaining sites the compiler named, all the same shape as the last commit: `metadata-protocol`'s seed-loader and search query plumbing hold parse results, so they name `XParsed`; `ISeedLoaderService`'s request/result/graph follow, and its contract test's mocks with them. Two test-typecheck ledgers move because the flip fixed what they recorded, not because anything was suppressed: @objectstack/spec 79 files / 691 errors -> 58 / 268 @objectstack/client 3 files / 6 errors -> 0 / 0 (ledger now empty) Both ledgers' own headers describe their contents as "fixture literals annotated with a schema OUTPUT type while holding an authored INPUT literal" — which is the defect ADR-0122 exists to end. No fixture was edited to achieve this; the bare name simply means the input type now. The four entries that reached zero are deleted, which is what the shrink-only ratchet requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
The FROM→TO section claimed the compiler named "1 file outside packages/spec". It named 40, across 1127 candidate files — still the point being made (every one was a compile error, none silent), but the number was wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
…uilds `resolveHeldScopes` already normalises every adminScope flag to a boolean (`!== false` / `=== true`) and the allowlist to a `string[]`, so `HeldScope.scope` is the PARSED shape and now says so. The previous commit had reached for `?? true` / `?? false` at the report boundary instead — correct output, but dead code that reads like a default being applied where none is needed. The authored scope, which really does arrive as raw JSON with any subset of the flags stated, keeps the bare `AdminScope` at the two `parseMaybeJson` sites. Both containment paths there already read it with `!== false` / `=== true`, so the author state was the honest type for them all along — the flip is what made the two shapes distinguishable enough to say which is which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
…sions, regen Four text conflicts, each adjudicated by a machine check rather than by eye: - ui/i18n.zod.ts, ui/widget.zod.ts — took main's post-retirement content wholesale (#5055 retired 13 of the widget/i18n family), then re-ran the phase-2 codemod over the WHOLE spec tree. `check:spec-parsed-alias` proves the result: every surviving bare alias reads `z.input`. That sweep also caught a new bare `z.infer` alias main had added in ui/component.zod.ts — it auto-merged clean and would have violated the inverted gate silently. - type-alias-convention.pin.test.ts — the two sides documented DIFFERENT true things about the count, so the resolution composes both: main's paragraph on retirement (-4, and why a retirement is not the deletion that case exists to stop) and phase 2's on the gate widening (+35 with no schema change behind it). The count is recomputed from source, never reasoned about: 751. - test-typecheck-debt.json — took main's and re-recorded AFTER the merge rather than mid-merge, per its own header. 58 files / 267 errors, shrink-only respected. Two collisions had NO text conflict and would have shipped: * `M170` was bound twice — main's #5775 to ui/component.zod.js, phase 2's to automation/builtin-node-config.zod.js. Different regions of the import list, so git merged both. The pin registry parser keeps the LAST binding, which silently re-pointed phase 2's `ScreenFieldConfigSchema` pin at the wrong module; check:spec-parsed-alias reported it as a paired missing-pin/stale-pin. * `Iso719` was declared twice — main's #5775 addition and the first pin of phase 2's block. tsc caught it (TS2300) via tsconfig.test.json. Both are the same shape: two branches appending to one sequence. Phase 2's imports renumbered to M172/M173 and its pins to Iso720..Iso754; main's numbering untouched. Registers the XInput retirement in the ADR-0087 ledger as `spec-type-alias-input-suffix-retired`, with the #6148 disposition marker on the changeset. Required, not optional: the changeset carries a FROM -> TO prescription, which closes the `no-migration-prescription` escape by design. The entry's own justification is the `actor-user-roles-to-positions` (#6011) one — a TypeScript name with no carrier key and no tombstone, where tsc names the removal but is mute about the replacement, so the generated upgrade guide is the only channel carrying the second half. All 10 generated artifacts regenerated from the merged tree and verified current. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
…e guide Two defects visible only in the generated output, caught by reading it: - `surface` carried backticks. `build-upgrade-guide.ts` renders that field INSIDE a code span (and again inside a table cell), so the nested backticks broke the span — the reader saw stray backticks mid-sentence. Every neighbouring entry writes `surface` as plain text for this reason (`actor-user-roles-to-positions` is `'action body / AI route: ctx.user.roles (req.user.roles)'`); this one now matches, and is shortened so the table cell stays readable. - the `rg` command in `acceptanceCriteria` had lost a backslash in escaping and read `\bw+Input\b` — it matches the literal `w`, so a reader running the acceptance check verbatim would get no hits and conclude the migration was already done. Now `\b\w+Input\b`. Regenerated spec-changes.json and the upgrade guide from the corrected entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
`check:type-check-debt` caught 19 raw errors the strict-zone typecheck could not: both packages exclude their own tests, so these live behind a DEBT/TEST_DEBT entry and only surface on the ratchet's re-measure. Every one is the same misalignment already fixed in the strict packages, so all 19 are FIXED — neither ledger entry is raised, and neither `note` needed rewriting. @objectstack/core, DEBT 111 -> 98 (the recorded number, exactly): All 13 were TS2345, a code the entry's note did not list at all — the pile's composition is unchanged otherwise (code-tier still exactly 3: TS18046/TS2739/ TS2352; config-tier 23; noise 72). The fixtures in health-monitor.test.ts and hot-reload.test.ts write every key of their config explicitly — they ARE parsed values — but were annotated with the bare name, which this branch made the author state. Annotating them `PluginHealthCheckParsed` / `HotReloadConfigParsed` restores exactly what they meant before the flip, which is also what `registerPlugin` takes. @objectstack/objectql, TEST_DEBT 361 -> 348: The gate reported +6 against the ledger's 355, but origin/main actually measures 351 — the ledger is 4 above main because of an improvement someone else landed and did not re-record. Measured against main rather than against the stale number, this branch added 13 and removed 3, and all 13 are the same shape: `EngineQueryOptions` annotations on query bags handed to `find`/`findOne`. The engine's parameter type is byte-identical to main's (`EngineQueryOptionsParsed` IS what main called `EngineQueryOptions`), so the fix is on the annotation, and it keeps these bags TYPED rather than cast — `hook-input-shape-contract.test.ts` and the #4918 ratchet both exist to stop an erased query-options bag, in test code included. The entry is deliberately NOT lowered to 348. Four of the seven points of headroom are that other author's improvement; harvesting them into this PR's ledger edit would bank someone else's work under this change's name. The ratchet only forbids upward drift, so leaving 355 is green and honest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
Fixes #6083
ADR-0122 phase 2:裸名从 author 语义(
z.input)翻转为 parsed 语义(z.infer),XInput同大版本退役,check:spec-parsed-alias门禁语义按翻转后世界反转(防翻转后「裸名 z.infer」计数归零的空洞绿)。含维护者已接受的两项自裁:XInput 同 major 退役、门禁反转。Generated by Claude Code